package ic.doc.co2nudge.client.utils;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.TreeMap;

import com.google.gwt.core.client.GWT;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.maps.client.geocode.DirectionResults;
import com.google.gwt.maps.client.geocode.Directions;
import com.google.gwt.maps.client.geocode.DirectionsCallback;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;

import ic.doc.co2nudge.client.utils.events.DataEvent;
import ic.doc.co2nudge.client.utils.events.DataLoadedEventHandler;
import ic.doc.co2nudge.client.utils.events.DataLoadedListener;
import ic.doc.co2nudge.client.utils.events.GeoDataEvent;
import ic.doc.co2nudge.client.utils.events.GeoDataLoadedEventHandler;
import ic.doc.co2nudge.client.utils.events.GeoDataLoadedListener;
import ic.doc.co2nudge.client.utils.events.StatisticalDataEvent;
import ic.doc.co2nudge.client.utils.events.StatisticalDataLoadedListener;
import ic.doc.co2nudge.shared.Constants;
import ic.doc.co2nudge.shared.GraphType;
import ic.doc.co2nudge.shared.model.DataType;
import ic.doc.co2nudge.services.DataService;
import ic.doc.co2nudge.services.DataServiceAsync;
import ic.doc.co2nudge.shared.model.ForecastData;
import ic.doc.co2nudge.shared.model.TransportType;
import ic.doc.co2nudge.shared.model.Trip;
import ic.doc.co2nudge.shared.model.TripOption;
import ic.doc.co2nudge.shared.model.User;
import ic.doc.co2nudge.shared.model.UserData;

public class DataServiceFactory {

	private static final DataServiceAsync dataService=
		GWT.create(DataService.class);
	
	private static List<TransportType> 				transportTypes;
	
	private static DataLoadedListener 				dataLoadedListener; 
	private static GeoDataLoadedListener 			geoDataLoadedListener;
	private static StatisticalDataLoadedListener 	statDataLoadedListener;
	
	public static DataServiceAsync getDataService(){
		return dataService;
	}
	
	public static void addDataLoadedListener(DataLoadedListener listener){
		dataLoadedListener=listener;
	}
	
	public static void addGeoDataLoadedListener(GeoDataLoadedListener listener){
		geoDataLoadedListener=listener;
	}
	
	public static void addStatisticalDataLoadedListener(StatisticalDataLoadedListener listener){
		statDataLoadedListener=listener;
	}
	
	public static void removeDataLoadedListener(){
		dataLoadedListener=null;
	}
	
	public static void removeGeoDataLoadedListener(){
		geoDataLoadedListener=null;
	}
	
	public static void removeStatDataLoadedListener(){
		statDataLoadedListener=null;
	}
	
	protected static void fireDataLoaded(DataEvent e){
		dataLoadedListener.dataLoaded(e);
	}
	
	protected static void fireGeoDataLoaded(GeoDataEvent e){
		geoDataLoadedListener.geoDataLoaded(e);
	}
	
	protected static void fireStatDataLoaded(StatisticalDataEvent e){
		statDataLoadedListener.dataLoaded(e);
	}
	
	public static void generate(){
		
		List<TransportType> transportTypes=new ArrayList<TransportType>();
		transportTypes.add( new TransportType("Bicycle"));
		transportTypes.add( new TransportType("Rail", 300));
		transportTypes.add( new TransportType("Small_Car", 5));
		transportTypes.add( new TransportType("Family_Car", 7));
		transportTypes.add( new TransportType("Bus", 25));
		transportTypes.add( new TransportType("Taxi", 4));
		transportTypes.add( new TransportType("Aircraft", 200));
		
//		List<NumOfPeopleType> numOfPeopleTypes=new ArrayList<NumOfPeopleType>();
//		numOfPeopleTypes.add( new NumOfPeopleType("Single adult",1));
//		numOfPeopleTypes.add( new NumOfPeopleType("Single adult + Child",2));
//		numOfPeopleTypes.add( new NumOfPeopleType("Double adults",2));
//		numOfPeopleTypes.add( new NumOfPeopleType("Family",4));
//		numOfPeopleTypes.add( new NumOfPeopleType("Group",20));
//		
//		List<BudgetType> budgetTypes=new ArrayList<BudgetType>();
//		budgetTypes.add( new BudgetType("Fixed"));
//		budgetTypes.add( new BudgetType("Expanding"));
//		
//		List<TripType> tripTypes=new ArrayList<TripType>();
//		tripTypes.add( new TripType("Short"));
//		tripTypes.add( new TripType("Medium"));
//		tripTypes.add( new TripType("Long"));
		
		generateDictionary(transportTypes);
	}

	public static void generateUsers(){
		
		User u1=new User();
		u1.setName("James");
		u1.setUserName("james");
		u1.setPassword("123");
		
		User u2=new User();
		u2.setName("Rosie");
		u2.setUserName("rosie");
		u2.setPassword("123");
		
		User u3=new User();
		u3.setName("Christian");
		u3.setUserName("chris");
		u3.setPassword("123");
		
		User u4=new User();
		u4.setName("Helen");
		u4.setUserName("helen");
		u4.setPassword("123");
		
		User u5=new User();
		u5.setName("John");
		u5.setUserName("john");
		u5.setPassword("123");
		
		List<User> users=
			new ArrayList<User>();
		
		users.add(u1);
		users.add(u2);
		users.add(u3);
		users.add(u4);
		users.add(u5);
		
		DataServiceFactory.removeDataLoadedListener();
		DataServiceFactory.addDataLoadedListener( new DataLoadedEventHandler(){
			
			@Override
			public void dataLoaded(DataEvent e){
				if (e.isCompletedSuccessfully())
					Window.alert("Saved");
				else
					Window.alert("Not Saved");
			}
		});
		
		DataServiceFactory.saveUsers(users);
	}
	
	public static void loadDictionaryData() {
		dataService.loadDictionaryData( new AsyncCallback<HashMap<String, List<? extends DataType>>>(){

			@Override
			public void onFailure(Throwable caught) {
				fireDataLoaded(new DataEvent(false));
			}

			@Override
			public void onSuccess(
					HashMap<String, List<? extends DataType>> dictionary) {
				
				if ((dictionary!=null)
						&&(dictionary.size()>0)) {
					
					transportTypes=(List<TransportType>)dictionary
						.get(TransportType.class.getName());
				}
				fireDataLoaded(new DataEvent(true));
			}
		});
	}
	
	public static List<TransportType> getTransportTypes() {
		return transportTypes;	
	}
	
	public static TransportType getTransportTypeById(Long id) {
		
		Window.alert( id.toString());
		
		TransportType type=null;
		
		for (TransportType t: getTransportTypes()) {
			
			if (t.getId().equals(  id)) {
				type=t;
				return type;
			}
		}
		return type;	
	}
	
	private static void getTripDistance(String from, String to){
		
		final String fromAddress=from.trim();
		final String toAddress=to.trim();
		
		String query= "from: " + fromAddress+ " to: " + toAddress;
		
		Directions.load(query, new DirectionsCallback(){

			@Override
			public void onFailure(int statusCode) {
				fireDataLoaded(new DataEvent(false));
			}

			@Override
			public void onSuccess(DirectionResults routes) {
				
				if ((routes!=null)
						&&(routes.getRoutes()!=null)){
					
					double minRoute=routes.getRoutes().get(0)
						.getDistance().inMeters();
										Double tempRouteDistance=0D;
					
					for (int i=1; i<routes.getRoutes().size(); i++){
						tempRouteDistance=routes.getRoutes()
							.get(i).getDistance().inMeters();
						
						if (tempRouteDistance<minRoute)
							minRoute=tempRouteDistance;
					}
					
					GeoDataEvent e=new GeoDataEvent();
					e.setCompletedSuccessfully(true);
					e.setDistance( Utils.round( minRoute));
					fireGeoDataLoaded(e);
				}
			}			
		});
	}
	
	public static void generateTripOptions(String from, String to, int numOfPeople){
		
		final int num=numOfPeople;
		
		addGeoDataLoadedListener( new GeoDataLoadedEventHandler(){
			
			@Override
			public void geoDataLoaded(GeoDataEvent e){
				
				if (e.getCompletedSuccessfully()){
					
					Double distance=0D;
					distance=(Double)e.getDistance();
					
					List<TripOption> options=getTripOptions(distance, num);
					DataEvent ee=new DataEvent(false);
					
					if (options!=null){
						
						HashMap<String, Object> data=
							new HashMap<String, Object>();
						
						data.put("Distance", distance);
						data.put("Options", options);
						
						ee.setData(data);
						ee.setCompletionStatus(true);
					}
					
					removeGeoDataLoadedListener();
					fireDataLoaded(ee);
				}
			}
		});

		getTripDistance(from, to);
	}

	private static List<TripOption> getTripOptions(Double distance, int numOfPeople){
		
		List<TripOption> options=
			new ArrayList<TripOption>();
		
		for (TransportType t: getTransportTypes()) {
			
			if ((t.getMaxNumOfSeats()<numOfPeople)
					||((t.getName().equals("Aircraft"))
							&&(distance<Constants.getConstants()
									.getConstant("MinimalDistanceForFlight"))))
				continue;
			
			String name=t.getName();
			Integer c=Constants.getConstants()
				.getConstant(name);
			
			if (c==null)
				continue;
			
			double rate=Constants.getConstants().getConstant(name)
				*(distance/1000);
			
			rate=Utils.round(rate);
			
			TripOption option=new TripOption();
			option.setTransportType(t);
			option.setRate(rate/1000);
			
			options.add( option);
		}
		
		return options;
	}
	
	private static void generateDictionary(
			List<TransportType> transportTypes){
		
		dataService.storeDictionary(transportTypes, new AsyncCallback(){

				@Override
				public void onFailure(Throwable caught) {
					fireDataLoaded(new DataEvent(false));
				}

				@Override
				public void onSuccess(Object result) {
					fireDataLoaded(new DataEvent(true));
				}
		});
	}

	public static void saveTrip(Trip trip){
		
		dataService.storeTrip(trip, UserService.getUserService().getCurrentUser(),
				new AsyncCallback<Boolean>(){

					@Override
					public void onFailure(Throwable caught) {
						DataEvent e=new DataEvent(false);
						fireDataLoaded(e);
					}

					@Override
					public void onSuccess(Boolean isStored) {
						DataEvent e;
						if (isStored)
							e=new DataEvent(true);
						else
							e=new DataEvent(false);
						fireDataLoaded(e);
					}
		});
	}

	public static void saveUser(User user){
		dataService.storeUser(user, new AsyncCallback(){

			@Override
			public void onFailure(Throwable caught) {
				DataEvent e=new DataEvent(false);
				fireDataLoaded(e);
			}

			@Override
			public void onSuccess(Object result) {
				DataEvent e=new DataEvent(true);
				fireDataLoaded(e);
			}
		});
	}
	
	public static void saveUsers(List<User> users){
		dataService.storeUsers(users, new AsyncCallback(){

			@Override
			public void onFailure(Throwable caught) {
				// TODO Auto-generated method stub
				DataEvent e=new DataEvent(false);
				fireDataLoaded(e);
			}

			@Override
			public void onSuccess(Object result) {
				// TODO Auto-generated method stub
				DataEvent e=new DataEvent(false);
				fireDataLoaded(e);
			}
		});
	}
	
	public static void loadStatisticalData(GraphType type){
		
		final GraphType gType=type;
		
		dataService.loadStatisticalData(type, 
				new AsyncCallback< HashMap<String, Double>>(){

			StatisticalDataEvent e =
				new StatisticalDataEvent();
			
			@Override
			public void onFailure(Throwable caught) {	
				fireStatDataLoaded(e);
			}

			@Override
			public void onSuccess( HashMap<String, Double> result) {
				if (result!=null) {
					e.setStatData(result);
					e.setGraphType(gType);
					e.setCompletionStatus(true);
				}
				fireStatDataLoaded(e);
			}
		});
	}

	public static void loadPredictionData(User user) {
		
		DateTimeFormat ff=DateTimeFormat.getFormat("MM/yyyy");
		Date date=ff.parse("12/2010");
		
		dataService.loadPredictionData(user, date, 
				new AsyncCallback<ForecastData>(){

			@Override
			public void onFailure(Throwable caught) {
				fireDataLoaded(new DataEvent(false));
			}

			@Override
			public void onSuccess(ForecastData data) {
				
				DataEvent e;
				if ((data!=null)){
					e=new DataEvent(true);
					e.setData(data);
				}else
					e=new DataEvent(false);
				fireDataLoaded(e);	
			}
		});
	}

	public static void calcUsersCosts(){
		
		dataService.calcUsersCosts( new AsyncCallback<HashMap<String, Double>>(){

			DataEvent e=new DataEvent(false);
			
			@Override
			public void onFailure(Throwable caught) {
				fireDataLoaded(e);
			}

			@Override
			public void onSuccess(HashMap<String, Double> usersCosts) {
				e.setData(usersCosts);
				e.setCompletionStatus(true);
				fireDataLoaded(e);
			}
		});
	}
	
	public static void loadAnalysisData() {
		
		dataService.loadAnalysisData( new AsyncCallback<List<UserData>>() {

			DataEvent e=new DataEvent(false);
			
			@Override
			public void onFailure(Throwable caught) {
				fireDataLoaded(e);
			}

			@Override
			public void onSuccess(List<UserData> aData) {
				e=new DataEvent(true);
				e.setData(aData);
				fireDataLoaded( e);
			}
		});
	}
	
	
}